#include "Profiler.h"
#include <sstream>

const char* _t = "  ";

auto_struct::auto_struct( const char* n )
{
	// time begin
	Profiler::getTime(startTime);
	Profiler::getSingletonPtr()->functionStart(n);
}

auto_struct::~auto_struct()
{
	// time end
	Profiler::getTime(endTime);
	endTime -= startTime;
	assert(endTime > 0);
	Profiler::getSingletonPtr()->functionEnd(endTime);
}

Profiler::Profiler(void)
{
	root = new ProfilerNode;
	root->parent = 0;
	currentNode = root;
	//root->data = NULL;
	PerformanceData* data = new PerformanceData("root");
	data->nCalls = 0;
	root->data = data;
	childIndex = 0;
}

Profiler::~Profiler(void)
{
	delete root;
}

#ifdef _WIN32
#include <Windows.h>
inline void Profiler::getTime(largeint& i)
{
	QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&i));
}
#endif

void Profiler::functionStart(const char* n)
{
	assert(n&&currentNode);
	// first check if nested function
	if (currentNode->data&&currentNode->data->name == n)
	{
		currentNode->isNested++; // set flag to nested, so functionEnd will know
		currentNode->data->nCalls++;
		return;
	}
	// check if this function already exist
	ProfilerNode* node = 0;
	std::vector<ProfilerNode*>::iterator iter;
	for (iter=currentNode->children.begin();iter!=currentNode->children.end();++iter)
	{
		if ((*iter)->data->name == n) // pointer compare
		{
			node = (*iter);
			break;
		}
	}
	if (node)
	{
		node->data->nCalls++;
		currentNode = node;
	}
	else
	{
		// create new node
		ProfilerNode* newNode = new ProfilerNode;
		newNode->parent = currentNode;
		currentNode->addChild(newNode);

		// create performance data
		PerformanceData* data = new PerformanceData(n);
		newNode->data = data;

		// set current node
		currentNode = newNode;
	}
}

void Profiler::functionEnd(largeint& espladedTime)
{
	assert(currentNode&&currentNode->data);
	if (currentNode->isNested)
	{
		currentNode->isNested--;
		return;
	}
	currentNode->data->totalTime += espladedTime;
	currentNode = currentNode->parent;
}

void Profiler::dump() const
{
	CSV oCsv("profiler.csv",CSV::mode_read|CSV::mode_write|CSV::mode_trunc);
	// write header
	oCsv.writeString("Profiler dump");
	oCsv.writeLine();
	oCsv.writeString("function name");
	oCsv.writeString("calls");
	oCsv.writeString("time total");
	oCsv.writeString("time per call");
	oCsv.writeString("percentage time");
	oCsv.writeLine();
	// write content
	std::string ts = "";
	float totalTime = dumpNode(root,oCsv,ts);
	assert(totalTime > 0);
	int row = 2;
	dumpNodeAfter(root,oCsv,row,totalTime);
	// end of file
	oCsv.close();
}

float Profiler::dumpNode( const ProfilerNode* node, CSV& file, std::string& format ) const
{
	assert(node&&node->data);
	float totalTime = getMsTime(node->data->totalTime);
	writeContent(file,format,node->data->name,node->data->nCalls,totalTime);
	format += _t;

	if (node->children.size() == 0)
	{
		format = format.substr(0,format.size() - 2);
	}
	else
	{
		float childrenTime = 0;
		std::vector<ProfilerNode*>::const_iterator iter;
		for (iter=node->children.begin();iter!=node->children.end();++iter)
		{
			childrenTime += dumpNode((*iter),file,format);
		}

		// root does not have left over time
		if (node == root)
		{
			return childrenTime;
		}
		else
		{
			// left over time
			std::string str = "<";
			str += node->data->name;
			str += ">";
			float leftOverTime = totalTime - childrenTime;
			writeContent(file,format,str,node->data->nCalls,leftOverTime);
		}
		
		// - "/t"
		format = format.substr(0,format.size() - 2);
	}

	return totalTime;
}

float Profiler::dumpNodeAfter( const ProfilerNode* node,CSV& file,int& row,float totalTime ) const
{
	assert(node&&node->data);
	float mytotalTime = getMsTime(node->data->totalTime);
	file.seekTo(row++,4);
	float percent = (100*mytotalTime / totalTime);
	writePercentage(file,percent);
	float childrenTime = 0;
	std::vector<ProfilerNode*>::const_iterator iter;
	for (iter=node->children.begin();iter!=node->children.end();++iter)
	{
		childrenTime += dumpNodeAfter((*iter),file,row,totalTime);
	}
	if (node->children.size() > 0)
	{
		file.seekTo(row++,4);
		percent = (100*(mytotalTime - childrenTime)) / totalTime;
		writePercentage(file,percent);
	}
	if (node == root)
	{
		return childrenTime;
	}
	else
	{
		return mytotalTime;
	}
}

float Profiler::getMsTime( const largeint& i ) const
{
	LARGE_INTEGER frequency;
	QueryPerformanceFrequency(&frequency);
	double time = (double)(reinterpret_cast<const LARGE_INTEGER*>(&i)->QuadPart)/(double)frequency.QuadPart;
	return (float)time*1000;
}

void Profiler::writeContent( CSV& file,const std::string& format,const std::string& name, int calls, float totalms ) const
{
	file.writeStringWoComma(format);
	file.writeString(name);
	file.writeInt(calls);
	file.writeFloat(totalms); // time total
	float mspercall = 0;
	if (calls != 0)
	{
		mspercall = totalms / calls;
	}
	file.writeFloat(mspercall); // ms per call
	file.writeString("00.0%");
	file.writeLine();
}

void Profiler::writePercentage( CSV& file,float percent ) const
{
	std::stringstream ss;
	ss.precision(1);
	if (percent < 10)
	{
		ss << " " << std::fixed << percent;
	}
	else
	{
		ss << std::fixed << percent;
	}
	ss << "%";
	file.writeString(ss.str()); // percentage time
}